package org.momo.rpc.extention;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static java.nio.charset.StandardCharsets.UTF_8;


@Slf4j
public class ExtensionLoader<T> {

    public static final String SCAN_BASE_PACKAGE = "/META-INF/rpc/";

    private static final Map<Class<?>, ExtensionLoader<?>> cachedExtensionLoaders = new ConcurrentHashMap<>();

    private final Map<String, Class<?>> cachedClasses = new ConcurrentHashMap<>();

    private final Map<String, Object> cachedInstances = new ConcurrentHashMap<>();

    private final Class<?> inface;

    private ExtensionLoader(Class<T> inface) {
        this.inface = inface;
    }

    /**
     * 根据class获取对应的ExtensionLoader
     */
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> clazz) {
        ExtensionLoader<?> extensionLoader = cachedExtensionLoaders.get(clazz);
        if (extensionLoader == null) {
            synchronized (cachedExtensionLoaders) {
                extensionLoader = cachedExtensionLoaders.get(clazz);
                if (extensionLoader == null) {
                    extensionLoader = new ExtensionLoader<>(clazz);
                    cachedExtensionLoaders.put(clazz, extensionLoader);
                }
            }
        }
        return (ExtensionLoader<T>) extensionLoader;
    }

    /**
     * 根据serviceImplName获取对应的实例
     * e.g: serializer => JsonSerializer
     */
    public T getExtension(String extensionName) throws IllegalArgumentException {
        if (extensionName == null || StrUtil.isBlank(extensionName)) {
            throw new IllegalArgumentException("Extension name is null or empty");
        }

        Object instance = cachedInstances.get(extensionName);
        if (instance == null) {
            synchronized (cachedInstances) {
                instance = cachedInstances.get(extensionName);
                if (instance == null) {
                    instance = createExtension(extensionName);
                    cachedInstances.put(extensionName, instance);
                }
            }
        }

        return (T) instance;
    }

    private T createExtension(String extensionName) {
        if (cachedClasses.isEmpty()) {
            getExtensionClasses();
        }

        Class<?> serviceImplClass = cachedClasses.get(extensionName);
        if (serviceImplClass == null) {
            log.error("No extension for interface, the interface is {}, the extension is {}", inface, extensionName);
            throw new IllegalArgumentException();
        }

        try {
            return (T) serviceImplClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            log.error("Create extension error! the interface is: {}, the extension is: {}, he error is: {}", inface, extensionName, e.getMessage());
            throw new RuntimeException("");
        }
    }

    private void getExtensionClasses() {
        if (cachedClasses.isEmpty()) {
            synchronized (cachedClasses) {
                if (cachedClasses.isEmpty()) {
                    loadClasses();
                }
            }
        }
    }

    private void loadClasses() {
        String fileName = ExtensionLoader.SCAN_BASE_PACKAGE + inface.getName();
        ClassLoader classLoader = inface.getClassLoader();

        try {
            // get resource file
            InputStream stream = inface.getResourceAsStream(fileName);
            if (stream == null) {
                throw new RuntimeException();
            }

            // read from resource
            BufferedReader reader = new BufferedReader(new InputStreamReader(stream, UTF_8));
            String line; String key; String className = "";

            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (!line.isEmpty()) {
                    try {
                        String[] split = line.split("=");
                        key = split[0]; className = split[1];
                        if (!key.isEmpty() && !className.isEmpty()) {
                            Class<?> clazz = classLoader.loadClass(className);
                            cachedClasses.put(key, clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        log.error("No definition for class! the class name is: {}, the interface is: {}", className, inface);
                    }
                }
            }
        } catch (Exception e) {
            log.error("Load interface error! the interface is {}, the error is: {}", inface, e.getMessage());
        }
    }

}
